﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using SmallMediaLibrary;
using System.Threading;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;

namespace SmallVideoPlayer
{
    public partial class Form1 : Form
    {
        VideoPlayer player;
        VideoFrame frame1;

        object o = new object();

        public Form1()
        {
            InitializeComponent();
        }

        protected override void OnResize(EventArgs e)
        {
            RecreateFrame();
            base.OnResize(e);
        }

        private void RecreateFrame()
        {
            lock (o)
            {
                panel1.Width = this.Width - 40;
                panel1.Height = this.Height - 100;

                frame1 = new VideoFrame(panel1.Width, panel1.Height);
                frame1.Buffer = GCHandle.Alloc(new byte[frame1.BufferLength], GCHandleType.Pinned).AddrOfPinnedObject();

                videoSource = new SimpleVideoSource(player, panel1.Width, panel1.Height);
             
                if(player != null)
                    player.SetReceivingFrame(frame1);
                }

                panelGraphics = panel1.CreateGraphics();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            OpenFileDialog dlg = new OpenFileDialog();
            var res = dlg.ShowDialog();

            if (res == System.Windows.Forms.DialogResult.OK)
            {
                panelGraphics = panel1.CreateGraphics();

                /* Create the video player and provide with a source, one load has returned the video can be started */
                if (player == null)
                {
                    player = new VideoPlayer();

                    /* Create a VideoFrame to receive the video data - the width and height are the dimensions of the bitmap whose buffer will be set in a minute */
                    frame1 = new VideoFrame(panel1.Width, panel1.Height);

                    /* Here, we allocate some memory for the video data.
                     * The video buffer must be set explicitly, a videoframe does not not allocate its own. It does however have default values for the region and pixelformat,
                     * and can determine the buffer length and stride based on these and the dimensions provided above.
                     * If a width and height are not provided, the stride, length and destination region must be set  */
                    frame1.Buffer = GCHandle.Alloc(new byte[frame1.BufferLength], GCHandleType.Pinned).AddrOfPinnedObject();

                    /* When the VideoFrame is complete, set it as the active frame, it will receive frames so long as it has been set */
                    player.SetReceivingFrame(frame1);


                    /* Here, we change the destination region - this is the region in the VideoFrame the source is written to. You would use this, for example, if you had
                     * two video frames sharing the same buffer, and needed to specify which part of the frame each would write to.
                     * This command creates a small widescreen window at the top of the panel */
                    frame1.DestinationRegion = new global::Region(50, 0, panel1.Width - 100, 200);

                    /* This command overrides the source region, this can be used to essentially crop the source video, before its transferred into the destination region */
                    //            player.OverrideSourceRegion(new global::Region(10, 10, 100, 100));

                    //This command allows the videoplayer to run asynchronously to our application. A timer will prompt the host to collect video data at 25fps
                    //            refreshTimer = new System.Threading.Timer(new TimerCallback(this.timerCallback), new object(), 33, 40);

                    //This command causes the videoplayer to invoke a callback by emitting an event notification every time it writes a new frame. 
                    player.FrameReadyEvent += new VideoPlayer.FrameReady(player_FrameReadyEvent);

                    videoSource = new SimpleVideoSource(player, panel1.Width, panel1.Height);
                }

                player.Load(dlg.FileName);

                /* Having a valid frame is orthogonal to the players ability to run - if there is no valid frame the video data is simply discarded, if the frame is set
                 * before the video is loaded it is stored and used when the video starts */
                player.Play();

            }
        }

        void player_FrameReadyEvent()
        {
            timerCallback(null);
        }

        SimpleVideoSource videoSource;

        Graphics panelGraphics;
        System.Threading.Timer refreshTimer;
        
        private void timerCallback(object so)
        {
            /*
             * There are two locks to be accounted for: 
             * 1. is the lock on each frame. This is respected by VideoPlayer (that is, if its locked it wont write to it), however video player
             * will not block on it. (it will however set the lock while it is writing - this doesn't stop applications which support reading at 
             * the same time from doing so however, VideoPlayer supports this)
             * 2. the lock around the active frame in video player - this prevents the active frame from changing while it has been locked by 
             * VideoPlayer (and thus preventing it from being used anywhere else)
             * 
             * Here, we lock the frame while it is drawn because GDI does not support reading from a buffer while it is being written to. If the drawing
             * process takes too long, VideoPlayer will not be 'held up' but frames will be missed.
             */

            lock (o) //we lock this because the resize callback can (and will) change the frame, and then Lock.Release() will fail
            {
                VideoFrame frame;
                
                frame = videoSource.GetFrame(); //use videosource
                //frame = frame1;       //use direct

                frame.Lock.Wait();

                var bmp = new Bitmap(panel1.Width, panel1.Height, frame1.BufferStride, System.Drawing.Imaging.PixelFormat.Format32bppArgb, frame.Buffer);

                panelGraphics.DrawImage(bmp, new Point(0, 0));
                frame.Lock.Release();
            }
        }

        private void panel1_Paint(object sender, PaintEventArgs e)
        {

        }

        private void button2_Click(object sender, EventArgs e)
        {
            player.Pause();
        }

        private void button3_Click(object sender, EventArgs e)
        {
            player.Play();
        }

        private void button4_Click(object sender, EventArgs e)
        {
            player.Stop();
        }

        private void trackBar1_Scroll(object sender, EventArgs e)
        {
            float range = trackBar1.Maximum - trackBar1.Minimum;
            float position = (float)trackBar1.Value / range;

            float duration = (float)player.Duration;
            float newPosition = duration * position;

            player.Seek((int)(newPosition));
        }
    }
}
